package com.xiaoyun;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWrapper;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.cron.timingwheel.SystemTimer;
import cn.hutool.cron.timingwheel.TimerTask;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.sun.media.sound.MidiUtils;
import com.sun.media.sound.StandardMidiFileReader;

import javax.sound.midi.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.awt.event.KeyEvent.*;

/**
 * @author xiaoyun461
 * @date 2023/6/4 1:00
 */
public class MidiMain {

    private static final List<String> yysKey = Arrays.asList(
            "Q", "W", "E", "R", "T", "Y", "U",
            "A", "S", "D", "F", "G", "H", "J",
            "Z", "X", "C", "V", "B", "N", "M");
    private static final List<Integer> yysKeyVk = Arrays.asList(
            VK_Q, VK_W, VK_E, VK_R, VK_T, VK_Y, VK_U,
            VK_A, VK_S, VK_D, VK_F, VK_G, VK_H, VK_J,
            VK_Z, VK_X, VK_C, VK_V, VK_B, VK_N, VK_M
    );

    private static final List<Integer> midiList = Arrays.asList(
            48, 50, 52, 53, 55, 57, 59,
            60, 62, 64, 65, 67, 69, 71,
            72, 74, 76, 77, 79, 81, 83
    );

    public static void main(String[] args) throws InvalidMidiDataException, IOException {
        File midiFile = FileUtil.file(System.getProperty("user.dir"), "初音未来 - 千本樱.mid");

//        File midiCodeFile = FileUtil.file(System.getProperty("user.dir"), "midiCode.json");
//        JSON json = JSONUtil.readJSON(midiCodeFile, FileWrapper.DEFAULT_CHARSET);
//        HashBiMap<String, Integer> midiMap = HashBiMap.create(json.toBean(Map.class));
//        BiMap<Integer, String> inverseMidiMap = midiMap.inverse();

        StandardMidiFileReader standardMidiFileReader = new StandardMidiFileReader();
        Sequence sequence = standardMidiFileReader.getSequence(midiFile);
        Track[] tracks = sequence.getTracks();
        Map<String, List<Integer>> map = new HashMap<>();
        Map<Integer, List<Integer>> setNote = new HashMap<>();

        long microsecondLength = sequence.getMicrosecondLength();
        System.out.println("microsecondLength-持续时间:" + microsecondLength);


        for (Track track : tracks) {
            for (int i = 0; i < track.size(); i++) {
                long ticks = track.ticks();
                System.out.println("ticks:" + ticks);

                MidiEvent midiEvent = track.get(i);
                MidiMessage message = midiEvent.getMessage();
                int tempoMPQ = MidiUtils.getTempoMPQ(message);
                System.out.println("MidiUtils.getTempoMPQ:" + tempoMPQ);

                if (tempoMPQ != -1) {
                    System.out.println("MidiUtils.convertTempo:" + MidiUtils.convertTempo(tempoMPQ));
                }

                if (message instanceof ShortMessage) {
                    ShortMessage shortMessage = (ShortMessage) message;
                    long tick = midiEvent.getTick();
                    System.out.println("this-tick:" + tick);
                    long time = MidiUtils.tick2microsecond(sequence, tick, null);
                    System.out.println("time:" + time);

                    String noteKey = time / 1000 + "_" + shortMessage.getCommand();
                    List<Integer> list = map.get(noteKey);
                    if (CollUtil.isEmpty(list)) {
                        list = new ArrayList<>();
                    }

                    if (shortMessage.getCommand() == ShortMessage.NOTE_ON ||
                            shortMessage.getCommand() == ShortMessage.NOTE_OFF) {
                        int note = shortMessage.getData1();


                        list.add(note);
                        map.put(noteKey, list);

                        Integer baseNote = note / 12;
                        List<Integer> scaleSet = setNote.get(baseNote);
                        if (Objects.isNull(scaleSet)) {
                            scaleSet = new ArrayList<>();
                        }
                        scaleSet.add(note);
                        setNote.put(baseNote, scaleSet);


                    } else if (shortMessage.getCommand() == ShortMessage.MIDI_TIME_CODE) {
                        System.out.println("");
                    } else {
                        System.out.println("shortMessage.getCommand:" + shortMessage.getCommand());
                        System.out.println("shortMessage.getMessage:" + Arrays.toString(shortMessage.getMessage()));
                        System.out.println("shortMessage.getStatus:" + shortMessage.getStatus());
                        System.out.println("shortMessage.getChannel:" + shortMessage.getChannel());
                        System.out.println("shortMessage.getData1:" + shortMessage.getData1());
                        System.out.println("shortMessage.getData2:" + shortMessage.getData2());
                    }

                } else if (message instanceof MetaMessage) {
                    System.out.println("");
                }
            }
        }

        int defaultBaseNote = 5;
        int tempBaseNote = 5;
        int tempBaseNoteSize = Integer.MIN_VALUE;
        for (Map.Entry<Integer, List<Integer>> entry : setNote.entrySet()) {
            int size = entry.getValue().size();
            if (size > tempBaseNoteSize) {
                tempBaseNoteSize = size;
                tempBaseNote = entry.getKey();
            }
        }
        int offsetBaseNote = defaultBaseNote - tempBaseNote;


        Map<Integer, Integer> midiIndex = new HashMap<>();
        for (int i = 0; i < midiList.size(); i++) {
            midiIndex.put(midiList.get(i), i);
        }
        MidiConfig midiConfig = new MidiConfig();
        Integer belowLimit = midiConfig.getBelowLimit();
        Integer aboveLimit = midiConfig.getAboveLimit();
        Integer blackKey1 = midiConfig.getBlackKey1();
        Integer blackKey2 = midiConfig.getBlackKey2();
        Integer blackKey3 = midiConfig.getBlackKey3();

        Map<String, Set<Integer>> newMap = new HashMap<>();
        for (Map.Entry<String, List<Integer>> entry : map.entrySet()) {
            List<Integer> value = entry.getValue();

            Set<Integer> indexList = new HashSet<>();
            for (Integer item : value) {
                int tempItem = offsetBaseNote * 12 + item;

                if (tempItem < midiList.get(0) && belowLimit == 0) {
                    continue;
                }

                if (tempItem > midiList.get(midiList.size() - 1) && aboveLimit == 0) {
                    continue;
                }

                while (tempItem < midiList.get(0)) {
                    tempItem += 12;
                    if (belowLimit == 1) {
                        break;
                    }
                }

                while (tempItem > midiList.get(midiList.size() - 1)) {
                    tempItem -= 12;
                    if (aboveLimit == 1) {
                        break;
                    }
                }

                Integer index = midiIndex.get(tempItem);
                if (Objects.nonNull(index)) {
                    indexList.add(index);
                } else {
                    if (tempItem >= midiList.get(0) && tempItem <= midiList.get(6)) {
                        if (blackKey1 == 1) {
                            indexList.add(midiIndex.get(tempItem - 1));
                        } else if (blackKey1 > 1) {
                            indexList.add(midiIndex.get(tempItem + 1));
                            if (blackKey1 == 3) {
                                indexList.add(midiIndex.get(tempItem + 1) - 1);
                            }
                        }
                    }
                    if (tempItem >= midiList.get(7) && tempItem <= midiList.get(13)) {
                        if (blackKey2 == 1) {
                            indexList.add(midiIndex.get(tempItem - 1));
                        } else if (blackKey2 > 1) {
                            indexList.add(midiIndex.get(tempItem + 1));
                            if (blackKey2 == 3) {
                                indexList.add(midiIndex.get(tempItem + 1) - 1);
                            }
                        }
                    }
                    if (tempItem >= midiList.get(14) && tempItem <= midiList.get(20)) {
                        if (blackKey3 == 1) {
                            indexList.add(midiIndex.get(tempItem - 1));
                        } else if (blackKey3 > 1) {
                            indexList.add(midiIndex.get(tempItem + 1));
                            if (blackKey3 == 3) {
                                indexList.add(midiIndex.get(tempItem + 1) - 1);
                            }
                        }
                    }
                }
            }
            if (CollUtil.isEmpty(indexList)) {
                continue;
            }
            newMap.put(entry.getKey(), indexList);

        }

        SystemTimer systemTimer = new SystemTimer();


        for (Map.Entry<String, Set<Integer>> entry : newMap.entrySet()) {
            String key = entry.getKey();
            Set<Integer> value = entry.getValue();


            String time = key.split("_")[0];
            String status = key.split("_")[1];

            ThreadUtil.execAsync(() -> systemTimer.addTask(new TimerTask(() -> {
                thisTask(Convert.toInt(status), value, time);
            }, 10000 + Convert.toLong(time))));

        }
        systemTimer.start();
        System.out.println(map);

    }


    private static void thisTask(Integer status, Set<Integer> value, String
            thisTime) {

        String statusStr = Objects.equals(status, ShortMessage.NOTE_ON) ? "按下" : "弹起";
        System.out.println(
                "当前时间:" + thisTime +
                        " 当前状态:" + statusStr +
                        " 当前键位:" + value);

        try {
            Robot robot = new Robot();
            if (Objects.equals(status, ShortMessage.NOTE_ON)) {
                for (Integer index : value) {
                    // E5-> indexOf(E) , 5  -> 2 , 5  -> (2 + 1) + (5 - 4) * 7 - 1  -> 9 ->  list.get(9) -> D
//                    int keyIndex =
//                            Convert.toInt(midiKey.indexOf(str.substring(0, 1)) + 1) +
//                                    ((Convert.toInt(str.substring(str.length() - 1)) - minKey) * midiKey.size());
                    System.out.println("当前正在按下:" + yysKey.get(index));
                    robot.keyPress(yysKeyVk.get(index));
                }
            } else {
                for (Integer index : value) {
//                    if (excludeSet.contains(str)) {
//                        continue;
//                    }
//                    int keyIndex =
//                            Convert.toInt(midiKey.indexOf(str.substring(0, 1)) + 1) +
//                                    ((Convert.toInt(str.substring(str.length() - 1)) - minKey) * midiKey.size());
                    System.out.println("当前正在弹起:" + yysKey.get(index));
                    robot.keyRelease(yysKeyVk.get(index));
                }
            }
        } catch (AWTException e) {
            throw new RuntimeException(e);
        }
    }


}
